package ru.ifmo.network;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * @author avhaliullin
 */
public class HostsStorage implements HelloMessageListener {
    public static final String ACTION_DISCONNECTED = "disconnected";
    public static final String ACTION_ALIVE = "alive";
    public static final String ACTION_HOST_NAME = "host_name";
    public static final String ACTION_FILE_LIST = "file_list";

    private final ConcurrentHashMap<String, HostInfo> hostsMap = new ConcurrentHashMap<String, HostInfo>();
    private final int port;

    private ActionListener listener;

    public HostsStorage(int port) {
        this.port = port;
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                for (HostInfo info : new ArrayList<HostInfo>(hostsMap.values())) {
                    if (info.getLastUpdate().getTime() + 3000 < new Date().getTime()) {
                        hostsMap.remove(info.getAddress());
                        if (listener != null) {
                            listener.actionPerformed(new ActionEvent(info, 0, ACTION_DISCONNECTED));
                        }
                    }
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
    }

    public void message(HelloMessage message) {
        HostInfo old = hostsMap.get(message.getHostAddress());
        List<String> files = null;
        if (old == null || old.getHash() != message.getHashCode()) {
            files = getFileList(message.getHostAddress());
        } else {
            files = old.getFiles();
        }
        HostInfo newInfo = new HostInfo(message.getHostName(), message.getHashCode(), new Date(), message.getHostAddress(), files);

        Logger.getAnonymousLogger().info("Hash: " + newInfo.getHash());
        hostsMap.put(message.getHostAddress(), newInfo);
        if (listener != null) {
            listener.actionPerformed(new ActionEvent(newInfo, 0, ACTION_ALIVE));
            if (old != null) {
                if (!old.getName().equals(newInfo.getName())) {
                    listener.actionPerformed(new ActionEvent(newInfo, 0, ACTION_HOST_NAME));
                }
                if (old.getHash() != newInfo.getHash()) {
                    listener.actionPerformed(new ActionEvent(newInfo, 0, ACTION_FILE_LIST));
                }
            }
        }
    }

    public Downloader getFile(String hostName, String fileName) {
        HostInfo hostInfo = hostsMap.get(hostName);
        if (hostInfo == null) {
            return null;
        }
        FiniteInputStreamHolder file = Requester.getFile(hostName, port, fileName);
        if (file == null) {
            return null;
        }
        return new Downloader(file.getInputStream(), file.getSize(), hostInfo.getName(), fileName);
    }

    private List<String> getFileList(String hostAddress) {
        return Requester.listFiles(hostAddress, port);
    }

    public void setListener(ActionListener listener) {
        this.listener = listener;
    }

    class HostInfo {
        private final String name;
        private final int hash;
        private final Date lastUpdate;
        private final String address;
        private final List<String> files;

        private HostInfo(String name, int hash, Date lastUpdate, String address, List<String> files) {
            this.name = name;
            this.hash = hash;
            this.lastUpdate = lastUpdate;
            this.address = address;
            this.files = files;
        }

        public String getName() {
            return name;
        }

        public int getHash() {
            return hash;
        }

        public Date getLastUpdate() {
            return lastUpdate;
        }

        public String getAddress() {
            return address;
        }

        public List<String> getFiles() {
            return files;
        }

        public String toString() {
            return getName() + " [" + getAddress() + "]";
        }

        public boolean equals(Object o) {
            if (o == null || !(o instanceof HostInfo)) {
                return false;
            }

            HostInfo info = (HostInfo) o;
            return info.getAddress().equals(address) && info.getHash() == hash && info.getName().equals(name);
        }
    }
}
